package com.example.aikit_plugin;

import android.app.Activity;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import androidx.annotation.NonNull;

import com.hjq.permissions.OnPermission;
import com.hjq.permissions.XXPermissions;
import com.iflytek.aikit.core.AiAudio;
import com.iflytek.aikit.core.AiHandle;
import com.iflytek.aikit.core.AiHelper;
import com.iflytek.aikit.core.AiListener;
import com.iflytek.aikit.core.AiRequest;
import com.iflytek.aikit.core.AiResponse;
import com.iflytek.aikit.core.AiStatus;
import com.iflytek.aikit.core.BaseLibrary;
import com.iflytek.aikit.core.CoreListener;
import com.iflytek.aikit.core.ErrType;
import com.iflytek.aikit.core.LogLvl;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

/** AikitPlugin */
public class AikitPlugin implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ActivityAware {
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private MethodChannel channel;
  private Context mContext = null;
  private Activity mactivity = null;
  public static EventChannel.EventSink mEventSink = null;
  private EventChannel eventChannel;
  // 下为唤醒所需
  private String RES_DIR = "/sdcard/iflytek/ivw";
  private final String TAG = "AEELog";
  private AtomicBoolean isEnd = new AtomicBoolean(true);
  private AtomicBoolean isStartRecord = new AtomicBoolean(false);
  private String audioPath;
  private AudioRecord audioRecord = null;
  //录音缓冲区大小
  private final int BUFFER_SIZE = 1280;
  //录音是否在进行
  private AtomicBoolean isRecording = new AtomicBoolean(false);
  private AiHandle aiHandle;
  private static final int START = 0x0001;
  private static final int WRITE_BY_RECORDING = 0x0002;
  private static final int WRITE_BY_READFILE = 0x0003;
  private static final int END = 0x0004;
  private Handler mHandler;
  private static final String ABILITYID = "e867a88f2";
  private String keyword = "你好小迪";

  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    if (null == mContext) {
      mContext = flutterPluginBinding.getApplicationContext();
    }
    channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "aikit_plugin");
    channel.setMethodCallHandler(this);
    /**
     * 回调监听通道
     */
    eventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "aikit_plugin_stream");
    eventChannel.setStreamHandler(this);
  }

  @Override
  public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
    if (call.method.equals("getPlatformVersion")) {
      result.success("Android " + android.os.Build.VERSION.RELEASE);
    }else  if (call.method.equals("initSDK")) {
      AiHelper.getInst().setLogInfo(LogLvl.ERROR, 1, "/sdcard/iflytek/aikit/aeeLog.txt");
      String APPID = call.argument("APPID");
      String APIKEY = call.argument("APIKEY");
      String APISECRET = call.argument("APISECRET");
      //设定初始化参数
      BaseLibrary.Params params = BaseLibrary.Params.builder()
              .appId(APPID)  //您的应用ID，可从控制台查看
              .apiKey(APIKEY) //您的APIKEY，可从控制台查看
              .apiSecret(APISECRET) //您的APISECRET，可从控制台查看
              .workDir("/sdcard/iflytek/") //SDK的工作目录，需要确保有读写权限。一般用于存放离线能力资源，日志存放目录等使用。
              .build();
      //初始化SDK
      new Thread(new Runnable() {
        @Override
        public void run() {
          AiHelper.getInst().initEntry(mContext,params);
        }
      }).start();
      AiHelper.getInst().registerListener(coreListener);// 注册SDK 初始化状态监听
    } else if(call.method.equals("startRecord")){
      keyword = call.argument("keyword");
      startRecord();
    }else if(call.method.equals("stopRecord")){
      stopRecord();
    } else {
      result.notImplemented();
    }
  }

  /**
   * 根据录音数据计算音量
   */
  private int calculateVolume(byte[] buffer) {
    double sumVolume = 0.0;
    double avgVolume = 0.0;
    int volume = 0;
    for (int i = 0; i < buffer.length; i += 2) {
      int v1 = buffer[i] & 0xFF;
      int v2 = buffer[i + 1] & 0xFF;
      int temp = v1 + (v2 << 8);// 小端
      if (temp >= 0x8000) {
        temp = 0xffff - temp;
      }
      sumVolume += Math.abs(temp);
    }
    avgVolume = sumVolume / buffer.length / 2;
    volume = (int) (Math.log10(1 + avgVolume) * 10);
    return volume;
  }

  //授权结果回调
  private CoreListener coreListener = (type, code) -> {
    Log.i("TAG", "core listener code:" + code);

    mactivity.runOnUiThread(new Runnable() {
      @Override
      public void run() {
        switch (type) {
          case AUTH:
            if(code == 0){
              Map<String, Object> map =new HashMap<>();
              AiHelper.getInst().registerListener(ABILITYID, edListener);
              map.put("result", "");
              map.put("code", String.valueOf(code));
              map.put("success", true);
              map.put("type", "AUTH");
              mEventSink.success(map);
            }else{
              Map<String, Object> map =new HashMap<>();
              map.put("result", "");
              map.put("code", String.valueOf(code));
              map.put("success", false);
              map.put("type", "AUTH");
              mEventSink.success(map);
            }
            break;
          case HTTP:
            Map<String, Object> map =new HashMap<>();
            map.put("result", "");
            map.put("code", String.valueOf(code));
            map.put("success", true);
            map.put("type", "HTTP");
            mEventSink.success(map);
            break;
          default:
            Map<String, Object> map1 =new HashMap<>();
            map1.put("code", code);
            map1.put("success", false);
            map1.put("type", "default");
            mEventSink.success(map1);
        }
      }
    });
  };

  /**
   * 能力监听回调
   */
  private AiListener edListener = new AiListener() {
    @Override
    public void onResult(int handleID, List<AiResponse> outputData, Object usrContext) {
      if (null != outputData && outputData.size() > 0) {
        Log.i(TAG, "onResult:handleID:" + handleID + ":" + outputData.size() + "," +
                "usrContext:" + usrContext);
        for (int i = 0; i < outputData.size(); i++) {
          Log.d(TAG,"onResult:handleID:" + handleID + ":" + outputData.get(i).getKey());
          String key   = outputData.get(i).getKey();   //引擎结果的key
          byte[] bytes = outputData.get(i).getValue(); //识别结果
          String result = new String(bytes);
          Log.d(TAG, "key="+key);
          Log.d(TAG, "value="+result);
          Log.d(TAG, "status="+outputData.get(i).getStatus());
          if ((key.equals("func_wake_up") || key.equals("func_pre_wakeup"))) {
            Map<String, Object> map =new HashMap<>();
            map.put("result", result);
            map.put("code", key);
            map.put("success", true);
            map.put("type", "wake_up");
            mactivity.runOnUiThread(new Runnable() {
              @Override
              public void run() {
                mEventSink.success(map);
              }
            });
          }
        }
      }
    }

    @Override
    public void onEvent(int i, int i1, List<AiResponse> list, Object o) {
      Log.i(TAG, "onEvent:" + i + ",event:" + i1);

    }

    @Override
    public void onError(int i, int i1, String s, Object o) {
      Log.i(TAG,"错误通知，能力执行终止,Ability " + i + " ERROR::" + s + ",err code:" + i1);
    }
  };

  private void startRecord(){
    mThread.start();
    getPermission();
  }
  private void stopRecord(){
    Message msg = new Message();
    msg.what = WRITE_BY_RECORDING;
    msg.obj = AiStatus.END;
    mHandler.sendMessage(msg);
  }

  private Thread mThread = new Thread(new Runnable() {
    @Override
    public void run() {
      Looper.prepare();
      mHandler = new Handler(Looper.myLooper()){

        @Override
        public void handleMessage(@NonNull Message msg) {
          super.handleMessage(msg);
          switch(msg.what){
            case START:
              /**
               * 开始会话
               */
              Log.d(TAG,"START");
              int ret = start();
              if(ret == 0){
                int type = msg.arg1;
                if(type == WRITE_BY_RECORDING){
                  createAudioRecord();//创建录音器
                  isRecording.set(true);
                  audioRecord.startRecording();//录音器启动录音
                }
                mHandler.removeCallbacksAndMessages(null);//清空消息队列
                Message write_msg = new Message();
                write_msg.what = type;
                write_msg.obj = AiStatus.BEGIN;
                mHandler.sendMessage(write_msg);//调用write方法送音频数据给引擎
              }
              break;
            case WRITE_BY_RECORDING:
              /**
               * 写入数据-录音方式
               */
              AiStatus status =(AiStatus)msg.obj;
              byte data[] = new byte[BUFFER_SIZE];
              int read = audioRecord.read(data, 0, BUFFER_SIZE);
              //平方和除以数据总长度，得到音量大小。
              final int volume = calculateVolume(data);
//              Log.i("miao volume",volume+"");
              if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                //处理录音数据
                write(data, status);//送尾帧
              }
              if(AiStatus.END == status){
                if (audioRecord != null) {
                  audioRecord.stop();
                  isRecording.set(false);
                  mHandler.sendEmptyMessage(END);
                }
              }else{
                if(isRecording.get()){
                  Message write_msg = new Message();
                  write_msg.what = WRITE_BY_RECORDING;
                  write_msg.obj = AiStatus.CONTINUE;
                  mHandler.sendMessage(write_msg);//调用write方法送音频数据给引擎
                }
              }
              break;
            case END:
              /**
               * 结束会话
               */
              Log.d(TAG,"END");
              end();
              break;
          }
        }
      };
      Looper.loop();
    }
  });


  /**
   * 创建录音器
   */
  private void createAudioRecord() {
    if (isRecording.get()) {
      return;
    }
    if (audioRecord == null) {
      Log.d(TAG,"createAudioRecord");
      audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
              16000,
              AudioFormat.CHANNEL_IN_MONO,
              AudioFormat.ENCODING_PCM_16BIT,
              BUFFER_SIZE);
    }
  }

  /**
   * 开始会话
   */
  private int start(){
    if(!keyword2File()){
      Log.i(TAG,"唤醒词文件写入失败，请检查是否有读写权限。");
      return -1;
    }
    AiRequest.Builder customBuilder = AiRequest.builder();
    customBuilder.customText("key_word", RES_DIR + "/keyword.txt",0);
    int ret = AiHelper.getInst().loadData(ABILITYID, customBuilder.build());
    if (ret != 0) {
      Log.i(TAG,"open ivw loadData 失败：" + ret);
      return ret;
    }
    Log.i(TAG,"open ivw loadData  success：" + ret);
    int[] indexs = {0};
    ret = AiHelper.getInst().specifyDataSet(ABILITYID, "key_word", indexs);//从缓存中把个性化资源设置到引擎中
    if (ret != 0) {
      Log.i(TAG,"open ivw specifyDataSet 失败：" + ret);
      return ret;
    }

    Log.i(TAG,"open ivw specifyDataSet  success：" + ret);

    AiRequest.Builder paramBuilder = AiRequest.builder();
    paramBuilder.param("wdec_param_nCmThreshold", "0 0:800");
    paramBuilder.param("gramLoad", true);
    isEnd.set(false);
    aiHandle = AiHelper.getInst().start(ABILITYID, paramBuilder.build(), null);
    if (aiHandle.getCode() != 0) {
      Log.i(TAG,"open ivw start失败：" + aiHandle.getCode());
      return aiHandle.getCode();
    }
    return 0;
  }

  /**
   * 结束会话
   */
  private void end() {
    if (!isEnd.get()) {
      int ret = AiHelper.getInst().end(aiHandle);
      if (ret == 0) {
        isEnd.set(true);
        aiHandle = null;
        Log.i(TAG,"唤醒完成，end： " + ret);
      } else {
        isEnd.set(false);
        Log.i(TAG,"唤醒完成，end： " + ret);
      }
    }
  }
  /**
   * 写入数据
   */
  private  void write(byte[] part, AiStatus status) {
    if (isEnd.get()) {
      return;
    }
    AiRequest.Builder dataBuilder = AiRequest.builder();
    int ret = 0;

    /**
     * 送入音频需要标识音频的状态，第一帧为起始帧，status要传AiStatus.BEGIN,最后一帧为结束帧，status要传AiStatus.END,其他为中间帧，status要传AiStatus.CONTINUE
     * 音频要求16bit，16K，单声道的pcm音频。
     * 建议每次发送音频间隔40ms，每次发送音频字节数为一帧音频大小的整数倍。
     */
    AiAudio aiAudio = AiAudio.get("wav").data(part).status(status).valid();
    dataBuilder.payload(aiAudio);

    ret = AiHelper.getInst().write(dataBuilder.build(), aiHandle);
    if (ret != 0) {
      Log.i(TAG,"write失败：" + ret + " ");
    }
  }

  private boolean keyword2File() {
    try {
      // 构建目标文件夹路径
      File keywordFile = new File(RES_DIR + "/keyword.txt");
      if (keywordFile.exists()) {
        //强制清空内容
        keywordFile.delete();
      }
      File binFile = new File(RES_DIR + "/keyword.bin");
      if (binFile.exists()) {
        binFile.delete();
      }
      String str = keyword.replace("，", ",");
      String[] keywords = str.split(",");
      if (!keywordFile.exists()) {
        keywordFile.createNewFile();
      }
      OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(keywordFile),
              "UTF-8");
      BufferedWriter bufferedWriter = new BufferedWriter(writer);
      for (int i = 0; i < keywords.length; i++) {
        bufferedWriter.write(keywords[i]);
        bufferedWriter.write(";");
        bufferedWriter.newLine();//写入换行
      }
      bufferedWriter.close();
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    }
    return true;
  }

  private void getPermission(){
    /**
     * 获取录音权限。
     * 录音权限:Demo需要从麦克风获取录音数据用于演示唤醒，命令词等能力
     */
    XXPermissions.with(mactivity).permission("android.permission.RECORD_AUDIO","android.permission.WRITE_EXTERNAL_STORAGE","android.permission.MANAGE_EXTERNAL_STORAGE").request(new OnPermission() {
      @Override
      public void hasPermission(List<String> granted, boolean all) {
        Log.d(TAG,"SDK获取系统权限成功:"+all);
        for(int i=0;i<granted.size();i++){
          Log.d(TAG,"获取到的权限有："+granted.get(i));
        }
        if(all){
          isStartRecord.set(false);
          Message msg = new Message();
          msg.what = START;
          msg.arg1 = WRITE_BY_RECORDING;
          mHandler.sendMessage(msg);
        }
      }

      @Override
      public void noPermission(List<String> denied, boolean quick) {
        if(quick){
          Log.e(TAG,"onDenied:被永久拒绝授权，请手动授予权限");
          XXPermissions.startPermissionActivity(mContext,denied);
        }else{
          Log.e(TAG,"onDenied:权限获取失败");
        }
      }
    });
  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
  }

  @Override
  public void onListen(Object arguments, EventChannel.EventSink events) {
    mEventSink = events;
  }

  @Override
  public void onCancel(Object arguments) {

  }

  @Override
  public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
    mactivity = binding.getActivity();
  }

  @Override
  public void onDetachedFromActivityForConfigChanges() {

  }

  @Override
  public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {

  }

  @Override
  public void onDetachedFromActivity() {

  }
}
